﻿////////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2008 Piers Lawson
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Text;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace ResourcesOverMvc.Web.Mvc
{
    public class JsonSerializer
    {
        private WebConfigJavaScriptConverterEngine _jsonConverterEngine;

        /// <summary>
        /// For now hardcoded to always return an engine that looks in the virtual path
        /// </summary>
        public WebConfigJavaScriptConverterEngine JsonConverterEngine
        {
            get { return _jsonConverterEngine ?? (_jsonConverterEngine = new WebConfigJavaScriptConverterEngine()); }
        }

        /// <summary>
        /// Turn the data into a JSON string
        /// </summary>
        /// <param name="objectToSerialize"></param>
        /// <param name="controllerContext"></param>
        /// <returns></returns>
        public string Serialize(object objectToSerialize, ControllerContext controllerContext)
        {
            return InitialiseSerializer(controllerContext, null).Serialize(objectToSerialize);
        }

        /// <summary>
        /// Deserialize the JSON string into the target type
        /// </summary>
        /// <param name="serializedObject"></param>
        /// <param name="controllerContext"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        public object Deserialize(string serializedObject, ControllerContext controllerContext, Type targetType)
        {
            // Trick the serializer into calling the custom type resolver by supplying a dummy type
            var customTypeResolver = new CustomTypeResolver(targetType);
            return InitialiseSerializer(controllerContext, customTypeResolver).DeserializeObject("{\"__type\":\"AskTheCustomTypeResolver\"," + serializedObject.Substring(1));
        }

        /// <summary>
        /// Try to find the required transform or throw an exception listing the locations tried.
        /// </summary>
        private JavaScriptSerializer InitialiseSerializer(ControllerContext controllerContext, JavaScriptTypeResolver javaScriptTypeResolver)
        {
            // Try to find the converters
            var javaScriptConverterEngineResult = JsonConverterEngine.FindJsonConverters(controllerContext);

            if (javaScriptConverterEngineResult.Errors.Count > 0)
            {
                var builder = new StringBuilder();
                foreach (var str in javaScriptConverterEngineResult.Errors)
                {
                    builder.AppendLine();
                    builder.Append(str);
                }

                // Don't throw an exception if we are already inside an exception handling routine.
                // If we don't do this we could get into an infinite loop as the error raised causes another call
                // to the JsonSerializer.
                if (controllerContext.HttpContext.AllErrors == null)
                {
                    throw new InvalidOperationException(string.Format("There were errors whilst loading the JavaScriptConverters:{0}", new object[] { builder }));
                }
            }

            var serializer = javaScriptTypeResolver != null ? new JavaScriptSerializer(javaScriptTypeResolver) : new JavaScriptSerializer();
            serializer.MaxJsonLength = javaScriptConverterEngineResult.MaxJsonLength;
            serializer.RecursionLimit = javaScriptConverterEngineResult.RecursionLimit;
            serializer.RegisterConverters(javaScriptConverterEngineResult.Converters);

            return serializer;
        }

        /// <summary>
        /// This type resolver will only return the type it was constructed with
        /// </summary>
        private class CustomTypeResolver : JavaScriptTypeResolver
        {
            private readonly Type _typeToProvide;

            internal CustomTypeResolver(Type typeToProvide)
            {
                _typeToProvide = typeToProvide;
            }

            public override Type ResolveType(string id)
            {
                return _typeToProvide;
            }

            public override string ResolveTypeId(Type type)
            {
                throw new NotSupportedException();
            }
        }
    }
}